Skip to content

feat!: rewrite aitrack and opentrack in Rust (5.0) - #144

Merged
bircni merged 85 commits into
mainfrom
rust
Nov 9, 2026
Merged

bircni merged 85 commits into
mainfrom
rust

Conversation

@bircni

@bircni bircni commented Nov 8, 2026

Copy link
Copy Markdown
Owner

aitrack 5.0 replaces the TypeScript library, CLI and Node sidecar with Rust. The CLI ships as a native binary through npm (aitrack plus one @aitrack/cli-<platform> package per platform), and opentrack runs without the 144 MB sidecar.

BREAKING CHANGE: Node is no longer needed or used, and aitrack-lib is discontinued. The heatmap PNG uses an embedded font, completion scripts and argument-error wording change, and doctor no longer reports Node. Synced data, config, caches and opentrack settings stay compatible with 4.x, so 4.x and 5.0 machines can share one data repo.

bircni added 30 commits November 8, 2026 11:48
The window controls, resizing and stay-on-blur from #137 turned the popup
into a plain window. It is a popup again: fixed width, fitted to its
content, hidden on blur, with Keep open as the opt-in window mode. The
compact toggle moves to the footer and shows one limit per enabled
provider, falling back to weekly. Corners are rounded natively on macOS
and Windows 11.
Adds the core and CLI crates, a justfile and cargo-deny config, and a
Rust workflow for the rust branch. The 4.x pipeline also runs on rust
pushes so the TypeScript reference stays checked until the cut-over.
Machine files are shared between 4.x and 5.0 machines, so numbers follow
ECMAScript's Number::toString and sorted keys follow localeCompare.
Both are tested against tables generated by Node.
Day keys and local-day bucketing match 4.x across time zones, including
V8's day rollover and DST resolution, and are tested against a table of
Node results in five zones. Tests pass the zone instead of setting TZ.
Matches the 4.x library on tables it generated itself: model ids from the
pricing tables with every effort and tier suffix, remote URLs including
WHATWG path normalisation, and machine names.
Env, Paths, Clock and Output are passed in instead of read from globals,
so tests run isolated. Config reads and writes the same file 4.x does,
including symlinked configs and their mode.
Serialized key order and omitted optional fields match the machine files
4.x writes, and maps keep insertion order.
Renaming the package to aitrack-cli also renamed its binary; the smoke
test only passed against a stale build.
Scans, dedup, compaction deltas, project attribution and merging match
what 4.x makes of a recorded corpus in four time zones, with any chunk
size. Fields are read as leniently as 4.x casts them.
The edge-case log tests CRLF lines and an invalid byte, which end-of-line
conversion would otherwise rewrite.
Format 8 caches per file and per provider merge, keyed like 4.x's so both
coexist. Cached messages stay encoded until a resumed session needs them,
and a corrupt blob counts the file's days whole as 4.x does.
On 5.3 GB of real Claude logs it matches 4.x byte for byte on every
finished day, in 4.7 s and 119 MB against Node's 5.4 s and 381 MB.
Costs match 4.x bit for bit for every table model with the bundled tables
and with a newer pack, and on real logs priced with the cached pack. JSON
floats now parse exactly, which machine files and packs depend on.
…action

Validation, the writer, merging, rebucketing, compaction and repricing
match 4.x byte for byte, including the key order 4.x leaves when it adds
a cost to a day already flagged unpriced. Encrypted files open in both.
Every machine's days merged for reports, with stored costs kept and only
unpriced models backfilled, as 4.x merges them.
Pull, push with retry, per-day merges of conflicting sync commits and the
refusal to merge recompute commits run against real git in tests. 4.x and
the port write identical machine files and each accepts the other's repo
without a change.
Claude, Codex and Cursor limits, pacing, and Cursor's CSV export read with
the IDE's saved login from SQLite, past an exclusive lock.
Reports, comparisons, project breakdowns, top days, models and projects,
the usage-data loader under them, period windows and number formatting
match 4.x byte for byte in three time zones over a recorded data repo.
Quota mappers, pacing, value parsing, JWTs, the Cursor CSV and the order
of its credential attempts match Node's results. Timestamps accept the
offsets without a colon and the date-only Z that V8 accepts.
aitrack becomes a dependency-free launcher that runs the binary from its
platform's optional package, and downloads that binary from the release,
checked against checksums.txt, when npm skipped it.
The stats table, terminal tables, report CSV, receipt PDF, heatmap
figures and model display names render as 4.x does. Colour follows
chalk's detection, with NO_COLOR honoured. The receipt is written
directly in Courier with pdfkit's wrapping and truncation rules, so
there is no PDF dependency. PNG heatmaps sit behind an optional
`render` feature with an embedded Latin subset of DejaVu Sans.
Tables with and without colour, CSV, heatmap stats, grids and view
models, model names and the receipt's text runs, rules and page size
match Node's output. The heatmap is checked for its size and cell
colours, since glyphs differ from the canvas renderer.
Limits, usage, polling, alerts, settings and the tray picture now run in
the Tauri process on aitrack-core, so the app no longer ships or
supervises a 140 MB Node binary. cache.json format 2 and settings.json
stay readable by both versions, and the renderer's commands and events
are unchanged. Goldens recorded from the 4.x sidecar pin the tray pixels
and the settings and cache normalization.
Every command, with --json, exit codes and output matching 4.x. The
Node.js row leaves doctor since the binary no longer needs it.
4.x started these calls together; one at a time they made a cold scan
of a few hundred directories twice as slow as Node.
Debian's tzdata is built with backzone, which gives Pacific/Midway a
different pre-1901 offset than macOS and Node; Windows reads the home
from USERPROFILE.
bircni added 28 commits November 8, 2026 18:00
An update now releases the single-instance lock just before starting the
new copy instead of making it wait, and core's InstallTarget decides
where each OS installs and how it restarts for the app and the CLI. A
.deb install hears of a newer version again; only installing it fails.

Settings saves and the start-at-login switch each run on one ordered
worker; a save hands back its normalized settings and only the newest
save's outcome shows. The popup appears after a frame laid out at its
content, a kept-open window grows on its own screen, and the sync note
keeps the message it ended with.
eframe runs no UI frames for a hidden window, so waiting for one to
reveal it left the popup closed after the first hide; it is now shown
directly and hides only on real focus changes.
Every crate, opentrack included, now uses the workspace lints: rust,
rustdoc, clippy pedantic and nursery plus the restriction picks used in
the other bircni crates. Float rewrites stay off because costs must
round exactly as 4.x did.
Drops the workspace-wide allows: colours split bytes, the popup height
uses integer math, animations wrap their phase in f64 so long uptimes
stay smooth, and the remaining exact float checks are scoped to tests.
The PDF writer also falls back to '?' instead of truncating a code unit
WinAnsi cannot hold.
The settings screen and machine cards are split into sections, SVG arcs
are a struct, the CLI only exempts its two flag structs from the bool
lint, and the Win32 size casts name their bound.
…elper

The cache encoding, pricing store, fingerprint and recompute comparison
propagate or degrade instead of expecting.
The scan pool sorts indexed results, scans zip with their files, period
keys are an exhaustive match, rendering a PNG and validating counts
return errors, and the CLI parses years straight into the core's i16.
@bircni
bircni merged commit e57b200 into main Nov 9, 2026
3 checks passed
@bircni
bircni deleted the rust branch November 9, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant